Skip to main content

ResponseEntity in Spring Boot

Table of Contentsโ€‹

  1. What is ResponseEntity?
  2. Theory and Concepts
  3. Basic Usage
  4. ResponseEntity Methods
  5. Working with DTOs
  6. HTTP Status Codes
  7. Headers Management
  8. Best Practices
  9. Real-world Examples

What is ResponseEntity?โ€‹

ResponseEntity is a Spring Framework class that represents the entire HTTP response including the status code, headers, and body. It provides fine-grained control over the HTTP response in Spring Boot REST APIs.

Key Features:โ€‹

  • Complete HTTP Response Control: Status code, headers, and body
  • Type Safety: Generic type support for response body
  • Flexible: Can be used with or without response body
  • Builder Pattern: Fluent API for easy construction

Theory and Conceptsโ€‹

HTTP Response Structureโ€‹

HTTP/1.1 200 OK                    โ† Status Line
Content-Type: application/json โ† Headers
Content-Length: 85
Cache-Control: no-cache

{ โ† Body
"id": 1,
"name": "John Doe"
}

ResponseEntity Anatomyโ€‹

ResponseEntity<T> = Status Code + Headers + Body
  • Status Code: HTTP status (200, 404, 500, etc.)
  • Headers: HTTP headers (Content-Type, Authorization, etc.)
  • Body: Response payload (JSON, XML, plain text, etc.)

Basic Usageโ€‹

Simple ResponseEntityโ€‹

@RestController
public class UserController {

// Basic usage - just status
@GetMapping("/health")
public ResponseEntity<String> health() {
return ResponseEntity.ok("Service is running");
}

// With custom status
@PostMapping("/users")
public ResponseEntity<String> createUser() {
// Business logic here
return ResponseEntity.status(HttpStatus.CREATED)
.body("User created successfully");
}

// No content response
@DeleteMapping("/users/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
// Delete logic here
return ResponseEntity.noContent().build();
}
}

Constructor vs Builder Patternโ€‹

// Constructor approach
return new ResponseEntity<>("Hello World", HttpStatus.OK);

// Builder pattern (Recommended)
return ResponseEntity.ok()
.header("Custom-Header", "value")
.body("Hello World");

ResponseEntity Methodsโ€‹

Static Factory Methodsโ€‹

Success Responsesโ€‹

// 200 OK
ResponseEntity.ok()
ResponseEntity.ok("body")
ResponseEntity.ok().body(object)

// 201 Created
ResponseEntity.status(HttpStatus.CREATED)
ResponseEntity.created(uri)

// 204 No Content
ResponseEntity.noContent()

Error Responsesโ€‹

// 400 Bad Request
ResponseEntity.badRequest()
ResponseEntity.badRequest().body("Invalid input")

// 404 Not Found
ResponseEntity.notFound()

// 500 Internal Server Error
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)

Generic Statusโ€‹

ResponseEntity.status(HttpStatus.ACCEPTED)
ResponseEntity.status(202) // Status code as integer

Builder Methodsโ€‹

ResponseEntity.ok()
.header("X-Custom-Header", "value")
.contentType(MediaType.APPLICATION_JSON)
.cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS))
.body(responseBody);

Working with DTOsโ€‹

DTO Classesโ€‹

// User DTO
public class UserDTO {
private Long id;
private String name;
private String email;
private LocalDateTime createdAt;

// Constructors
public UserDTO() {}

public UserDTO(Long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
this.createdAt = LocalDateTime.now();
}

// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }

public String getName() { return name; }
public void setName(String name) { this.name = name; }

public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }

public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
}

// Request DTO
public class CreateUserRequestDTO {
@NotBlank(message = "Name is required")
private String name;

@Email(message = "Invalid email format")
@NotBlank(message = "Email is required")
private String email;

// Getters and Setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }

public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}

// Response Wrapper DTO
public class ApiResponseDTO<T> {
private boolean success;
private String message;
private T data;
private LocalDateTime timestamp;

public ApiResponseDTO(boolean success, String message, T data) {
this.success = success;
this.message = message;
this.data = data;
this.timestamp = LocalDateTime.now();
}

// Static factory methods
public static <T> ApiResponseDTO<T> success(T data) {
return new ApiResponseDTO<>(true, "Success", data);
}

public static <T> ApiResponseDTO<T> success(String message, T data) {
return new ApiResponseDTO<>(true, message, data);
}

public static <T> ApiResponseDTO<T> error(String message) {
return new ApiResponseDTO<>(false, message, null);
}

// Getters and Setters
public boolean isSuccess() { return success; }
public void setSuccess(boolean success) { this.success = success; }

public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; }

public T getData() { return data; }
public void setData(T data) { this.data = data; }

public LocalDateTime getTimestamp() { return timestamp; }
public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; }
}

Controller with DTOsโ€‹

@RestController
@RequestMapping("/api/users")
@Validated
public class UserController {

@Autowired
private UserService userService;

// GET - Single User
@GetMapping("/{id}")
public ResponseEntity<ApiResponseDTO<UserDTO>> getUserById(@PathVariable Long id) {
try {
UserDTO user = userService.findById(id);
if (user != null) {
return ResponseEntity.ok(ApiResponseDTO.success(user));
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponseDTO.error("User not found"));
}
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponseDTO.error("Internal server error"));
}
}

// GET - All Users with Pagination
@GetMapping
public ResponseEntity<ApiResponseDTO<List<UserDTO>>> getAllUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
try {
List<UserDTO> users = userService.findAll(page, size);
return ResponseEntity.ok(ApiResponseDTO.success("Users retrieved successfully", users));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponseDTO.error("Failed to retrieve users"));
}
}

// POST - Create User
@PostMapping
public ResponseEntity<ApiResponseDTO<UserDTO>> createUser(
@Valid @RequestBody CreateUserRequestDTO request) {
try {
UserDTO createdUser = userService.createUser(request);

URI location = URI.create("/api/users/" + createdUser.getId());

return ResponseEntity.created(location)
.header("X-Created-Resource", "User")
.body(ApiResponseDTO.success("User created successfully", createdUser));

} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest()
.body(ApiResponseDTO.error("Invalid input: " + e.getMessage()));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponseDTO.error("Failed to create user"));
}
}

// PUT - Update User
@PutMapping("/{id}")
public ResponseEntity<ApiResponseDTO<UserDTO>> updateUser(
@PathVariable Long id,
@Valid @RequestBody CreateUserRequestDTO request) {
try {
UserDTO updatedUser = userService.updateUser(id, request);
if (updatedUser != null) {
return ResponseEntity.ok(ApiResponseDTO.success("User updated successfully", updatedUser));
} else {
return ResponseEntity.notFound().build();
}
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest()
.body(ApiResponseDTO.error("Invalid input: " + e.getMessage()));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponseDTO.error("Failed to update user"));
}
}

// DELETE - Delete User
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponseDTO<Void>> deleteUser(@PathVariable Long id) {
try {
boolean deleted = userService.deleteUser(id);
if (deleted) {
return ResponseEntity.ok(ApiResponseDTO.success("User deleted successfully", null));
} else {
return ResponseEntity.notFound().build();
}
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponseDTO.error("Failed to delete user"));
}
}
}

HTTP Status Codesโ€‹

Common Status Codes in Spring Bootโ€‹

2xx Successโ€‹

// 200 OK - Request successful
ResponseEntity.ok(data)

// 201 Created - Resource created
ResponseEntity.status(HttpStatus.CREATED).body(data)

// 204 No Content - Success but no content to return
ResponseEntity.noContent().build()

// 202 Accepted - Request accepted for processing
ResponseEntity.accepted().build()

4xx Client Errorsโ€‹

// 400 Bad Request - Invalid request
ResponseEntity.badRequest().body("Invalid data")

// 401 Unauthorized - Authentication required
ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Authentication required")

// 403 Forbidden - Access denied
ResponseEntity.status(HttpStatus.FORBIDDEN).body("Access denied")

// 404 Not Found - Resource not found
ResponseEntity.notFound().build()

// 409 Conflict - Resource conflict
ResponseEntity.status(HttpStatus.CONFLICT).body("Resource already exists")

// 422 Unprocessable Entity - Validation errors
ResponseEntity.unprocessableEntity().body(validationErrors)

5xx Server Errorsโ€‹

// 500 Internal Server Error
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Server error")

// 503 Service Unavailable
ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body("Service temporarily unavailable")

Headers Managementโ€‹

Common Headersโ€‹

@GetMapping("/users/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
UserDTO user = userService.findById(id);

return ResponseEntity.ok()
.header("X-Total-Count", "1")
.header("X-Request-ID", UUID.randomUUID().toString())
.contentType(MediaType.APPLICATION_JSON)
.cacheControl(CacheControl.maxAge(300, TimeUnit.SECONDS))
.lastModified(user.getLastModified())
.eTag(user.getVersion().toString())
.body(user);
}

// Multiple headers
@GetMapping("/users")
public ResponseEntity<List<UserDTO>> getUsers() {
List<UserDTO> users = userService.findAll();

HttpHeaders headers = new HttpHeaders();
headers.add("X-Total-Count", String.valueOf(users.size()));
headers.add("X-Page-Number", "1");
headers.add("X-Page-Size", "10");
headers.add("Access-Control-Allow-Origin", "*");

return ResponseEntity.ok()
.headers(headers)
.body(users);
}

CORS Headersโ€‹

@CrossOrigin(origins = "http://localhost:3000")
@GetMapping("/users")
public ResponseEntity<List<UserDTO>> getUsers() {
List<UserDTO> users = userService.findAll();

return ResponseEntity.ok()
.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
.header("Access-Control-Allow-Headers", "Content-Type, Authorization")
.body(users);
}

Best Practicesโ€‹

1. Consistent Response Structureโ€‹

// Always use a consistent response wrapper
public class ApiResponse<T> {
private boolean success;
private String message;
private T data;
private Map<String, Object> metadata;

// Methods...
}

2. Proper Error Handlingโ€‹

@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(EntityNotFoundException.class)
public ResponseEntity<ApiResponseDTO<Void>> handleEntityNotFound(EntityNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponseDTO.error(ex.getMessage()));
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponseDTO<Map<String, String>>> handleValidation(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage()));

return ResponseEntity.badRequest()
.body(ApiResponseDTO.error("Validation failed").setData(errors));
}
}

3. Use Appropriate HTTP Methods and Status Codesโ€‹

@RestController
@RequestMapping("/api/users")
public class UserController {

@GetMapping("/{id}") // 200 OK or 404 NOT_FOUND
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) { /* ... */ }

@PostMapping // 201 CREATED
public ResponseEntity<UserDTO> createUser(@RequestBody UserDTO user) { /* ... */ }

@PutMapping("/{id}") // 200 OK or 404 NOT_FOUND
public ResponseEntity<UserDTO> updateUser(@PathVariable Long id, @RequestBody UserDTO user) { /* ... */ }

@DeleteMapping("/{id}") // 204 NO_CONTENT or 404 NOT_FOUND
public ResponseEntity<Void> deleteUser(@PathVariable Long id) { /* ... */ }
}

4. Location Header for Created Resourcesโ€‹

@PostMapping
public ResponseEntity<UserDTO> createUser(@RequestBody CreateUserRequestDTO request) {
UserDTO createdUser = userService.createUser(request);

URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(createdUser.getId())
.toUri();

return ResponseEntity.created(location).body(createdUser);
}

Real-world Examplesโ€‹

E-commerce Product APIโ€‹

@RestController
@RequestMapping("/api/products")
public class ProductController {

@Autowired
private ProductService productService;

@GetMapping
public ResponseEntity<PageResponseDTO<ProductDTO>> getProducts(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String category,
@RequestParam(required = false) String sortBy) {

PageResponseDTO<ProductDTO> products = productService.getProducts(page, size, category, sortBy);

return ResponseEntity.ok()
.header("X-Total-Elements", String.valueOf(products.getTotalElements()))
.header("X-Total-Pages", String.valueOf(products.getTotalPages()))
.cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS))
.body(products);
}

@PostMapping
public ResponseEntity<ApiResponseDTO<ProductDTO>> createProduct(
@Valid @RequestBody CreateProductRequestDTO request,
HttpServletRequest httpRequest) {

try {
ProductDTO product = productService.createProduct(request);

String location = httpRequest.getRequestURL().toString() + "/" + product.getId();

return ResponseEntity.status(HttpStatus.CREATED)
.header("Location", location)
.header("X-Resource-ID", product.getId().toString())
.body(ApiResponseDTO.success("Product created successfully", product));

} catch (ProductAlreadyExistsException e) {
return ResponseEntity.status(HttpStatus.CONFLICT)
.body(ApiResponseDTO.error("Product with this SKU already exists"));
}
}

@PatchMapping("/{id}/stock")
public ResponseEntity<ApiResponseDTO<ProductDTO>> updateStock(
@PathVariable Long id,
@RequestBody UpdateStockRequestDTO request) {

try {
ProductDTO updatedProduct = productService.updateStock(id, request.getQuantity());

return ResponseEntity.ok()
.header("X-Stock-Updated", "true")
.body(ApiResponseDTO.success("Stock updated successfully", updatedProduct));

} catch (InsufficientStockException e) {
return ResponseEntity.badRequest()
.body(ApiResponseDTO.error("Insufficient stock available"));
} catch (ProductNotFoundException e) {
return ResponseEntity.notFound().build();
}
}
}

File Upload/Download APIโ€‹

@RestController
@RequestMapping("/api/files")
public class FileController {

@PostMapping("/upload")
public ResponseEntity<ApiResponseDTO<FileDTO>> uploadFile(
@RequestParam("file") MultipartFile file) {

try {
if (file.isEmpty()) {
return ResponseEntity.badRequest()
.body(ApiResponseDTO.error("File cannot be empty"));
}

FileDTO uploadedFile = fileService.uploadFile(file);

return ResponseEntity.status(HttpStatus.CREATED)
.header("X-File-Size", String.valueOf(file.getSize()))
.header("X-File-Type", file.getContentType())
.body(ApiResponseDTO.success("File uploaded successfully", uploadedFile));

} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponseDTO.error("Failed to upload file"));
}
}

@GetMapping("/download/{id}")
public ResponseEntity<Resource> downloadFile(@PathVariable Long id) {
try {
FileResource fileResource = fileService.getFile(id);

return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(fileResource.getContentType()))
.contentLength(fileResource.getSize())
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + fileResource.getFilename() + "\"")
.body(fileResource.getResource());

} catch (FileNotFoundException e) {
return ResponseEntity.notFound().build();
}
}
}

Authentication APIโ€‹

@RestController
@RequestMapping("/api/auth")
public class AuthController {

@PostMapping("/login")
public ResponseEntity<ApiResponseDTO<LoginResponseDTO>> login(
@Valid @RequestBody LoginRequestDTO request) {

try {
LoginResponseDTO response = authService.authenticate(request);

return ResponseEntity.ok()
.header("Authorization", "Bearer " + response.getAccessToken())
.header("X-Token-Expires-In", String.valueOf(response.getExpiresIn()))
.body(ApiResponseDTO.success("Login successful", response));

} catch (BadCredentialsException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(ApiResponseDTO.error("Invalid credentials"));
}
}

@PostMapping("/refresh")
public ResponseEntity<ApiResponseDTO<TokenResponseDTO>> refreshToken(
@RequestHeader("Authorization") String refreshToken) {

try {
TokenResponseDTO response = authService.refreshToken(refreshToken);

return ResponseEntity.ok()
.header("Authorization", "Bearer " + response.getAccessToken())
.body(ApiResponseDTO.success("Token refreshed successfully", response));

} catch (TokenExpiredException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(ApiResponseDTO.error("Refresh token expired"));
}
}

@PostMapping("/logout")
public ResponseEntity<ApiResponseDTO<Void>> logout(
@RequestHeader("Authorization") String token) {

authService.logout(token);

return ResponseEntity.ok()
.header("X-Logout-Time", String.valueOf(System.currentTimeMillis()))
.body(ApiResponseDTO.success("Logout successful", null));
}
}

Conclusionโ€‹

ResponseEntity is a powerful tool in Spring Boot that provides complete control over HTTP responses. When combined with DTOs, it enables the creation of robust, type-safe REST APIs with proper status codes, headers, and response structures.

Key Takeaways:โ€‹

  • Always use appropriate HTTP status codes
  • Implement consistent response structures with DTOs
  • Handle errors gracefully with proper status codes
  • Use headers effectively for metadata and caching
  • Follow RESTful principles in your API design
  • Implement proper validation and error handling